---
id: org_comp
title: 1.3 弹框选择组件
sidebar_label: 1.3 弹框选择组件
---

import useBaseUrl from "@docusaurus/useBaseUrl";

前端针对组织架构做了组件封装，方便其他模块的调用

### 1.3.1 组件介绍

组件路径：src/comps/sys/OrgModal.vue

1） 快捷选择
<img src={useBaseUrl("img/core/01/03/01.png")}/>

:::note 页面说明
- 快捷选择中搜索可通过关键字快速搜索
- 快捷选择默认会展示最近使用过的组织架构元素
:::

2） 层级架构
<img src={useBaseUrl("img/core/01/03/02.png")}/>

:::note 页面说明
- 当想不起名称时可通过层级架构可以一层一层查询
:::

3） 常用群组

暂未开发

4） 我的收藏

暂未开发

## 1.3.2 单选的使用方式

以样例DEMO中客户信息为例，增加客户经办人。

修改src/pages/sa/cust/main/edit.vue

1） 增加客户经办人表单项

<img src={useBaseUrl("img/core/01/03/03.png")}/>

:::note 代码说明
- v-model绑定的是opmna，经办人姓名的计算属性
- 注意需要将input设成readonly
:::

2） 引入组织架构弹框

<img src={useBaseUrl("img/core/01/03/04.png")}/>

:::note 代码说明
- 引入的组件一般放在外层，防止被里面的样式影响
:::

3） 编写客户经办人弹框选择逻辑

<img src={useBaseUrl("img/core/01/03/05.png")}/>

:::note 代码说明
- region是一个注释，IDE可识别，可以收缩包裹的注释块。配合Vue3的 Composition API 可方便地维护一段独立逻辑
- 父组件拿到子组件的引用，调用子组件的openModal方法。就可以将弹框打开
- openModal方法的传值参数orgType是需要选择的组织架构类型，1：机构，2：部门，4：岗位，8：用户，16：群组。可组合，比如传3则代表可以选择机构与部门，传12则代表可以选择用户与岗位
- closeOrgModal是组织架构选择框点击确认后的回调，可根据实际情况做页面字段值的设置。
- opmna是一个计算属性，用计算属性的好处是可以将完整的经办人选择这个独立逻辑封装在一个块里。不然页面init时需要处理一些特殊逻辑。
:::

完整代码

```html
<template>
	<el-card class='box-card' :body-style="{padding:'2px 8px'}" shadow='never'>
		<template #header>
			<el-row>
				<el-col :span='10'>
					<div style='line-height: 32px'>客户维护</div>
				</el-col>
				<el-col :span='14' style='text-align: right'>
					<el-button type='success' @click='tabSave(state,proxy,route)' plain>保 存</el-button>
					<el-button type='info' @click='tabClose(proxy,route)' plain>关 闭</el-button>
				</el-col>
			</el-row>
		</template>
		<div style='margin-top: 8px;margin-bottom: 8px'>
			<el-form class='zform' :model='form' label-width='140px'>
				<el-tabs type='card' v-model='activeName'>
					<el-tab-pane label='基本信息' name='tab1'>
						<el-row style='border-top: 1px solid #d2d2d2;'>
							<el-col :span='24'>
								<el-form-item label='客户名称：' prop='name' :rules="[{ required: true, message: '名称不能为空'}]">
									<div class='zinput'>
										<el-input v-model='form.name'></el-input>
									</div>
								</el-form-item>
							</el-col>
						</el-row>
						<el-row>
							<el-col :span='12'>
								<el-form-item label='客户地址：'>
									<div class='zinput'>
										<el-input v-model='form.addre'></el-input>
									</div>
								</el-form-item>
							</el-col>
							<el-col :span='12'>
								<el-form-item label='是否可用：'>
									<div class='zinput'>
										<el-switch v-model='form.avtag'>
										</el-switch>
									</div>
								</el-form-item>
							</el-col>
						</el-row>
						<el-row>
							<el-col :span='24'>
								<el-form-item label='客户经办人：'>
									<div class='zinput'>
										<el-input v-model='opmna' @click='openOrgModal' readonly></el-input>
									</div>
								</el-form-item>
							</el-col>
						</el-row>
					</el-tab-pane>
					<el-tab-pane label='其他信息' name='tab3'>
						<el-row style='border-top: 1px solid #d2d2d2;'>
							<el-col :span='24'>
								<el-form-item label='备注：'>
									<div class='zinput' style='height: auto'>
										<el-input type='textarea' :rows='4' v-model='form.notes'>
										</el-input>
									</div>
								</el-form-item>
							</el-col>
						</el-row>
						<el-row v-show='form.crtim'>
							<el-col :span='6'>
								<el-form-item label='创建人：'>
									<div class='zinput'>
										{{ form.crman ? form.crman.name : '' }}
									</div>
								</el-form-item>
							</el-col>
							<el-col :span='6'>
								<el-form-item label='创建时间：'>
									<div class='zinput'>
										{{ form.crtim }}
									</div>
								</el-form-item>
							</el-col>
							<el-col :span='6'>
								<el-form-item label='更新人：'>
									<div class='zinput'>
										{{ form.upman ? form.upman.name : '' }}
									</div>
								</el-form-item>
							</el-col>
							<el-col :span='6'>
								<el-form-item label='更新时间：'>
									<div class='zinput'>
										{{ form.uptim }}
									</div>
								</el-form-item>
							</el-col>
						</el-row>
					</el-tab-pane>
				</el-tabs>
			</el-form>
		</div>
		<OrgModal ref='orgModal' @close='closeOrgModal' />
	</el-card>
</template>
<script lang='ts'>
export default { name: 'saCustMainEdit' };
</script>
<script lang='ts' setup>
import { computed, getCurrentInstance, onMounted, reactive, ref, toRefs } from 'vue';
import { editInit, tabSave, tabClose } from '/@/comps/page/edit';
import { useRoute } from 'vue-router';
import OrgModal from '/@/comps/sys/OrgModal.vue';

const route = useRoute();
const { proxy } = getCurrentInstance() as any;

const state = reactive({
	url: '/sa/cust/main',
	params: { path: '', query: '' }, checks: [] as any,
	form: { avtag: true, items: [] as any } as any,
});

const { form } = toRefs(state);
const activeName = ref('tab1');

onMounted(async () => {
	await editInit(state, route);
});


//region 经办人选择逻辑
const orgModal = ref();

const openOrgModal = () => {
	orgModal.value.openModal({
		orgType:8
	});
};

const closeOrgModal = (data: any) => {
	if (data.orgs && data.orgs.length > 0) {
		form.value.opman = { id: data.orgs[0].id, name: data.orgs[0].name };
		form.value.opmid = data.orgs[0].id;
	} else {
     	form.value.opman = null;
     	form.value.opmid = null;
    }
};

const opmna = computed(() => {
	let name = '';
	if (form.value.opman) {
		name=form.value.opman.name;
	}
	return name;
});
//endregion

</script>

<style scoped>

</style>
```

最终效果

<img src={useBaseUrl("img/core/01/03/06.png")}/>

<img src={useBaseUrl("img/core/01/03/07.png")}/>

要实现完整的功能，后端需要做如下修改

1）SaCustMain类增加两个字段

```csharp
     /// <summary>
     /// 经办人ID
     /// </summary>
     [SugarColumn(ColumnName = "opman",ColumnDescription = "经办人ID", IsNullable = true, Length = 36)]
     public string opmid { get; set; }

     /// <summary>
     /// 经办人
     /// </summary>
     [SugarColumn(IsIgnore = true)]
     public SysOrg opman { get; set; }
```

2）SaCustMainApi类增加经办人回显逻辑

```csharp
    /// <summary>
    /// 获取单个客户的详细信息
    /// </summary>
    /// <param name="id">客户ID</param>
    /// <returns></returns>
    public async Task<SaCustMain> GetOne(string id)
    {
        var main = await _service.SingleAsync(id);
        if (main.opmid != null)
        {
            main.opman = await _service.repo.Context.Queryable<SysOrg>()
                .Where(it => it.id == main.opmid).SingleAsync();
        }
        return main;
    }
```

## 1.3.3 多选的使用方式

以样例DEMO中客户信息为例，增加客户可查询人员。

修改src/pages/sa/cust/main/edit.vue

1）增加客户可查看者表单项

<img src={useBaseUrl("img/core/01/03/08.png")}/>

:::note 代码说明
- 多选时一般将input调成textarea
:::

2） 引入组织架构弹框

<img src={useBaseUrl("img/core/01/03/09.png")}/>

3） 编写客户可查询者弹框选择逻辑

<img src={useBaseUrl("img/core/01/03/10.png")}/>

:::note 代码说明
- openModal中的传值：orgType为11（1+2+8）则代表可选择机构，部门，用户；selectMode为2则代表多选，传1或者不传都代表单选；orgs为当前选择项的原始数据，多选时需要将它们返回给弹框做初始化，防止已选项的重新选择。
:::

完整代码

```html
<template>
	<el-card class='box-card' :body-style="{padding:'2px 8px'}" shadow='never'>
		<template #header>
			<el-row>
				<el-col :span='10'>
					<div style='line-height: 32px'>客户维护</div>
				</el-col>
				<el-col :span='14' style='text-align: right'>
					<el-button type='success' @click='tabSave(state,proxy,route)' plain>保 存</el-button>
					<el-button type='info' @click='tabClose(proxy,route)' plain>关 闭</el-button>
				</el-col>
			</el-row>
		</template>
		<div style='margin-top: 8px;margin-bottom: 8px'>
			<el-form class='zform' :model='form' label-width='140px'>
				<el-tabs type='card' v-model='activeName'>
					<el-tab-pane label='基本信息' name='tab1'>
						<el-row style='border-top: 1px solid #d2d2d2;'>
							<el-col :span='24'>
								<el-form-item label='客户名称：' prop='name' :rules="[{ required: true, message: '名称不能为空'}]">
									<div class='zinput'>
										<el-input v-model='form.name'></el-input>
									</div>
								</el-form-item>
							</el-col>
						</el-row>
						<el-row>
							<el-col :span='12'>
								<el-form-item label='客户地址：'>
									<div class='zinput'>
										<el-input v-model='form.addre'></el-input>
									</div>
								</el-form-item>
							</el-col>
							<el-col :span='12'>
								<el-form-item label='是否可用：'>
									<div class='zinput'>
										<el-switch v-model='form.avtag'>
										</el-switch>
									</div>
								</el-form-item>
							</el-col>
						</el-row>
						<el-row>
							<el-col :span='24'>
								<el-form-item label='客户经办人：'>
									<div class='zinput'>
										<el-input v-model='opmna' @click='openOrgModal' readonly></el-input>
									</div>
								</el-form-item>
							</el-col>
						</el-row>
						<el-row>
							<el-col :span='24'>
								<el-form-item label='可查看者：'>
									<div class='zinput' style='height: auto'>
										<el-input type='textarea' :rows='4' v-model='viewersName' readonly @click='openOrgsModal'>
										</el-input>
									</div>
								</el-form-item>
							</el-col>
						</el-row>
					</el-tab-pane>
					<el-tab-pane label='其他信息' name='tab3'>
						<el-row style='border-top: 1px solid #d2d2d2;'>
							<el-col :span='24'>
								<el-form-item label='备注：'>
									<div class='zinput' style='height: auto'>
										<el-input type='textarea' :rows='4' v-model='form.notes'>
										</el-input>
									</div>
								</el-form-item>
							</el-col>
						</el-row>
						<el-row v-show='form.crtim'>
							<el-col :span='6'>
								<el-form-item label='创建人：'>
									<div class='zinput'>
										{{ form.crman ? form.crman.name : '' }}
									</div>
								</el-form-item>
							</el-col>
							<el-col :span='6'>
								<el-form-item label='创建时间：'>
									<div class='zinput'>
										{{ form.crtim }}
									</div>
								</el-form-item>
							</el-col>
							<el-col :span='6'>
								<el-form-item label='更新人：'>
									<div class='zinput'>
										{{ form.upman ? form.upman.name : '' }}
									</div>
								</el-form-item>
							</el-col>
							<el-col :span='6'>
								<el-form-item label='更新时间：'>
									<div class='zinput'>
										{{ form.uptim }}
									</div>
								</el-form-item>
							</el-col>
						</el-row>
					</el-tab-pane>
				</el-tabs>
			</el-form>
		</div>
		<OrgModal ref='orgModal' @close='closeOrgModal' />
		<OrgModal ref='orgsModal' @close='closeOrgsModal' />
	</el-card>
</template>
<script lang='ts'>
export default { name: 'saCustMainEdit' };
</script>
<script lang='ts' setup>
import { computed, getCurrentInstance, onMounted, reactive, ref, toRaw, toRefs } from 'vue';
import { editInit, tabSave, tabClose } from '/@/comps/page/edit';
import { useRoute } from 'vue-router';
import OrgModal from '/@/comps/sys/OrgModal.vue';

const route = useRoute();
const { proxy } = getCurrentInstance() as any;

const state = reactive({
	url: '/sa/cust/main',
	params: { path: '', query: '' }, checks: [] as any,
	form: { avtag: true, items: [] as any } as any,
});

const { form } = toRefs(state);
const activeName = ref('tab1');

onMounted(async () => {
	await editInit(state, route);
});


//region 经办人选择逻辑
const orgModal = ref();

const openOrgModal = () => {
	orgModal.value.openModal({
		orgType: 8,
	});
};

const closeOrgModal = (data: any) => {
	if (data.orgs && data.orgs.length > 0) {
		form.value.opman = { id: data.orgs[0].id, name: data.orgs[0].name };
		form.value.opmid = data.orgs[0].id;
	} else {
		form.value.opman = null;
		form.value.opmid = null;
	}
};

const opmna = computed(() => {
	let name = '';
	if (form.value.opman) {
		name = form.value.opman.name;
	}
	return name;
});
//endregion

//region 可查询者选择逻辑
const orgsModal = ref();

const openOrgsModal = () => {
	orgsModal.value.openModal({
		orgType: 11,
		selectMode: 2,
		orgs: toRaw(form.value.viewers),
	});
};

const closeOrgsModal = (data: any) => {
	if (data.orgs && data.orgs.length > 0) {
		form.value.viewers = data.orgs;
	} else {
		form.value.viewers = null;
	}
};

const viewersName = computed(() => {
	let names = '';
	if (form.value.viewers && form.value.viewers.length > 0) {
		for (const user of form.value.viewers) {
			names += user.name + '；';
		}
	}
	return names;
});
//endregion

</script>

<style scoped>

</style>
```

最终效果

<img src={useBaseUrl("img/core/01/03/11.png")}/>

<img src={useBaseUrl("img/core/01/03/12.png")}/>

要实现完整的功能，后端需要做如下修改

1）SaCustMain类增加一个字段

```csharp
    /// <summary>
    /// 可查看者
    /// </summary>
    [SugarColumn(IsIgnore =true)]
    public List<SysOrg> viewers { get; set; }
```

2）sa/cust/main下创建一个新类SaCustMainViewer

```csharp
using SqlSugar;

namespace Vben.Application.Sa;

[SugarTable("sa_cust_main_viewer", TableDescription = "客户可查看者")]
public class SaCustMainViewer
{
    [SugarColumn(ColumnDescription = "客户ID",IsNullable = true)]
    public string mid{ get; set; }

    [SugarColumn(ColumnDescription = "可查看者ID",IsNullable = true)]
    public string oid{ get; set; }
}
```

3）SaCustMainService类提供专门的增删改方法

```csharp
    //新增
    public async Task<string> Insertx(SaCustMain main)
    {
        main.id = YitIdHelper.NextId() + "";
        var mappings = new List<SaCustMainViewer>();
        foreach (var viewer in main.viewers)
        {
            mappings.Add(new SaCustMainViewer {mid = main.id, oid = viewer.id});
        }

        using var tran = repo.Context.UseTran();
        await base.InsertAsync(main);
        await repo.Context.Insertable(mappings).ExecuteCommandAsync();
        tran.CommitTran();
        return main.id;
    }

    //修改
    public async Task<string> Updatex(SaCustMain main)
    {
        var mappings = new List<SaCustMainViewer>();
        foreach (var viewer in main.viewers)
        {
            mappings.Add(new SaCustMainViewer {mid = main.id, oid = viewer.id});
        }
        using var tran = repo.Context.UseTran();
        await base.UpdateAsync(main);
        await repo.Context.Deleteable<SaCustMainViewer>().Where(it => it.mid == main.id).ExecuteCommandAsync();
        await repo.Context.Insertable(mappings).ExecuteCommandAsync();
        tran.CommitTran();
        return main.id;
    }

    //删除
    public async Task Deletex(string ids)
    {
        using var tran = repo.Context.UseTran();
        var idArr = ids.Split(",");
        await repo.Context.Deleteable<SaCustMain>().In(idArr).ExecuteCommandAsync();
        for (int i = 0; i < idArr.Length; i++)
        {
            var mid = idArr[i];
            await repo.Context.Deleteable<SaCustMainViewer>().Where(it => it.mid == mid).ExecuteCommandAsync();
        }
        tran.CommitTran();
    }
```

:::note 代码说明
- 涉及多个表的操作，需要使用事务包裹。using var tran = repo.Context.UseTran(); --xxxxxx-- tran.CommitTran();
:::

4）SaCustMainApi类修改单体查询的方法，并使用Service的新增删改方法。

```csharp
    /// <summary>
    /// 获取单个客户的详细信息
    /// </summary>
    /// <param name="id">客户ID</param>
    /// <returns></returns>
    public async Task<SaCustMain> GetOne(string id)
    {
        var main = await _service.repo.Context.Queryable<SaCustMain>()
            .Mapper<SaCustMain, SysOrg, SaCustMainViewer>(it =>
                ManyToMany.Config(it.mid, it.oid))
            .Where(it => it.id == id).FirstAsync();
        if (main.opmid != null)
        {
            main.opman = await _service.repo.Context.Queryable<SysOrg>()
                .Where(it => it.id == main.opmid).SingleAsync();
        }
        return main;

    }

    /// <summary>
    /// 新增客户
    /// </summary>
    public async Task<string> Post(SaCustMain main)
    {
        return await _service.Insertx(main);
    }

    /// <summary>
    /// 修改客户
    /// </summary>
    public async Task<string> Put(SaCustMain main)
    {
        return await _service.Updatex(main);
    }

    /// <summary>
    /// 删除客户
    /// </summary>
    public async Task Delete(string ids)
    {
        await _service.Deletex(ids);
    }
```

:::note 代码说明
- 单体查询用到了SqlSugar的ManyToMany查询，也可以先查询出客户主体，再分步查询可查看者信息
:::

## 1.3.4 组件的复用

前面两个例子实例化了两个组织架构弹框，为了增加性能，可以只使用一次组织架构弹框，实现复用。

1）去除可查询者选择弹框。

<img src={useBaseUrl("img/core/01/03/13.png")}/>

2）修改代码，集中处理组织架构弹框选择逻辑

```csharp
//region 经办人与可查看者选择逻辑
const orgModal = ref();

const openOrgModal = () => {
	orgModal.value.openModal({
		opener: 'opman',
		orgType: 8,
	});
};

const openOrgsModal = () => {
	orgModal.value.openModal({
		opener: 'viewers',
		orgType: 11,
		selectMode: 2,
		orgs: toRaw(form.value.viewers),
	});
};

const closeOrgModal = (data: any) => {
	if(data.opener=="opman"){
		if (data.orgs && data.orgs.length > 0) {
			form.value.opman = { id: data.orgs[0].id, name: data.orgs[0].name };
			form.value.opmid = data.orgs[0].id;
		} else {
			form.value.opman = null;
			form.value.opmid = null;
		}
	}else if(data.opener=="viewers"){
		if (data.orgs && data.orgs.length > 0) {
			form.value.viewers = data.orgs;
		} else {
			form.value.viewers = null;
		}
	}
};

const opmna = computed(() => {
	let name = '';
	if (form.value.opman) {
		name = form.value.opman.name;
	}
	return name;
});

const viewersName = computed(() => {
	let names = '';
	if (form.value.viewers && form.value.viewers.length > 0) {
		for (const user of form.value.viewers) {
			names += user.name + '；';
		}
	}
	return names;
});
//endregion
```

:::note 代码说明
- 主要区别就是打开弹框时传递一个打开者的标记opener，关闭弹框的回调函数里同样通过这个标记来处理逻辑。
:::

